package com.heima.article.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.article.dto.ArticleDto;
import com.heima.article.dto.ArticleHomeDto;
import com.heima.article.dto.ArticleInfoDto;
import com.heima.article.dto.ArticleStreamMessage;
import com.heima.article.entity.ApArticle;
import com.heima.article.entity.ApArticleContent;
import com.heima.article.es.ApArticleSearch;
import com.heima.article.es.ArticleRepository;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.article.service.IApArticleContentService;
import com.heima.article.service.IApArticleService;
import com.heima.common.dto.ResponseResult;
import com.heima.common.enums.AppHttpCodeEnum;
import org.joda.time.DateTime;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;

/**
 * <p>
 * 文章信息表，存储已发布的文章 服务实现类
 * </p>
 *
 * @author djc
 * @since 2022-10-03
 */
@Service
public class ApArticleServiceImpl extends ServiceImpl<ApArticleMapper, ApArticle> implements IApArticleService {

    @Autowired
    private IApArticleContentService apArticleContentService;

    @Autowired
    private ArticleRepository articleRepository;

    @Override
    public ResponseResult<Long> saveArticle(ArticleDto dto) {
        if (dto.getId() == null) {// 新增
            ApArticle apArticle = new ApArticle();
            BeanUtils.copyProperties(dto, apArticle);
            apArticle.setCreatedTime(new Date());
            this.save(apArticle);

            ApArticleContent apArticleContent = new ApArticleContent();
            apArticleContent.setArticleId(apArticle.getId());
            apArticleContent.setContent(dto.getContent());

            apArticleContentService.save(apArticleContent);

            ApArticleSearch articleSearch = new ApArticleSearch();
            BeanUtils.copyProperties(apArticle, articleSearch);
            articleRepository.save(articleSearch);

            return ResponseResult.okResult(apArticle.getId());
        } else {  //修改
            ApArticle apArticle = new ApArticle();
            BeanUtils.copyProperties(dto, apArticle);
            apArticle.setCreatedTime(new Date());
            this.updateById(apArticle);

            LambdaQueryWrapper<ApArticleContent> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(ApArticleContent::getArticleId, dto.getId());

            ApArticleContent apArticleContent = apArticleContentService.getOne(queryWrapper);
            if (apArticleContent != null) {
                apArticleContent.setContent(dto.getContent());
                apArticleContentService.updateById(apArticleContent);
            }

            ApArticleSearch articleSearch = new ApArticleSearch();
            BeanUtils.copyProperties(apArticle, articleSearch);
            articleRepository.save(articleSearch);

            return ResponseResult.okResult(apArticle.getId());
        }


    }

    /**
     * 加载文章列表
     *
     * @param dto
     * @param type 0 最新  1  更多
     * @return
     */
    @Override
    public ResponseResult load(ArticleHomeDto dto, int type) {
//       判断是否为首页

        if (dto.getLoaddir() == 1) {
            String key = "hot_article_first_page_" + dto.getChannelId();
            long start = 0;
            long end = dto.getSize() - 1;
            Set<String> strings = redisTemplate.boundZSetOps(key).reverseRange(start, end);
            List<ApArticle> list = new ArrayList<>();
            for (String string : strings) {
                ApArticle apArticle = JSON.parseObject(string, ApArticle.class);
                list.add(apArticle);
            }
//            判断查询到的数据是否>=dto.getSize()   意味着Redis中的数量不够的话 就从mysql中查询了
            if (list != null && list.size() == dto.getSize()) {
                log.error(".........................文章数据都是从Redis中获取的........................");
                return ResponseResult.okResult(list);
            }

        }
        LambdaQueryWrapper<ApArticle> queryWrapper = new LambdaQueryWrapper<>();


        //        查询发布时间大于dto中的maxTime
        if (type == 0) {
            queryWrapper.gt(ApArticle::getPublishTime, dto.getMaxTime());
        } else {
            queryWrapper.lt(ApArticle::getPublishTime, dto.getMinTime());
        }
        //        如果dto中的channelId不为0时 根据频道查询
        if (dto.getChannelId() != 0) {
            queryWrapper.eq(ApArticle::getChannelId, dto.getChannelId());
        }
//        两个隐藏的条件 1、是否删除 2、是否下架
        queryWrapper.eq(ApArticle::getIsDelete, false);
        queryWrapper.eq(ApArticle::getIsDown, false);
//        根据发布时间降序查询
//        queryWrapper.orderByDesc(ApArticle::getPublishTime);
//        分页查询

        IPage<ApArticle> page = new Page<>(1, dto.getSize());
        IPage<ApArticle> page1 = this.page(page, queryWrapper);
        return ResponseResult.okResult(page1.getRecords());
    }

    @Override
    public ResponseResult loadArticleInfo(ArticleInfoDto dto) {

        // 根据id查询文章
        ApArticle apArticle = this.getById(dto.getArticleId());
        if (apArticle == null || apArticle.getIsDelete() || apArticle.getIsDown()) {
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST);
        }
        // 根据id查询文章内容
        LambdaQueryWrapper<ApArticleContent> query = new LambdaQueryWrapper<>();
        query.eq(ApArticleContent::getArticleId, dto.getArticleId());
        ApArticleContent articleContent = apArticleContentService.getOne(query);
        // 按照前端的格式拼接map
        Map<String, Object> map = new HashMap<>();
        map.put("config", apArticle);
        map.put("content", articleContent);
        // 返回通用的响应
        return ResponseResult.okResult(map);
    }

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public void compute() {
        Set<String> keys = redisTemplate.keys("hot_article_first_page_*");
        if (keys != null) {
            redisTemplate.delete(keys);
        }

        //        获取今天时间和 5天前的时间
        LambdaQueryWrapper<ApArticle> queryWrapper = Wrappers.lambdaQuery();
        //        使用了joda-time中的工具类 ，因为最近测试数据少，这里可以查询200天之前的数据
        Date beforeDate = DateTime.now().minusDays(300).toDate();
//        LambdaQueryWrapper<ApArticle> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ApArticle::getIsDelete, false);
        queryWrapper.eq(ApArticle::getIsDown, false);
        queryWrapper.ge(ApArticle::getPublishTime, beforeDate);

        List<ApArticle> articleList = this.list(queryWrapper);
        for (ApArticle article : articleList) {
            // 计算文章的分值
            int score = computeScore(article);

            ApArticle cache = new ApArticle();

            cache.setId(article.getId());
            cache.setTitle(article.getTitle());
            cache.setImages(article.getImages());
            cache.setAuthorId(article.getAuthorId());
            cache.setAuthorName(article.getAuthorName());
            cache.setLayout(article.getLayout());
            cache.setStaticUrl(article.getStaticUrl());

            //放的内容是固定不变的，比如点赞数、评论数经常变的属性就不要放了，如果理解不了就直接把article转成json字符串
            //            0代表是首页上推荐的文章
            redisTemplate.boundZSetOps("hot_article_first_page_0").add(JSON.toJSONString(cache), score);
            //            再放到各自的频道上推荐的文章
            redisTemplate.boundZSetOps("hot_article_first_page_" + article.getChannelId()).add(JSON.toJSONString(cache), score);

        }


    }


    private int computeScore(ApArticle article) {
        int score = 0;
        // 阅读 +1  点赞 +3  评论 +5  收藏 +8
        if (article.getViews() != null) {
            score += article.getViews() * 1;
        }
        if (article.getLikes() != null) {
            score += article.getLikes() * 3;
        }
        if (article.getComment() != null) {
            score += article.getComment() * 5;
        }
        if (article.getCollection() != null) {
            score += article.getCollection() * 8;
        }
        return score;
    }


    @Override
    public void updateHotArticleScore(ArticleStreamMessage articleStreamMessage) {
//        重新计算这次新增用户操作的分值
        Long newScore = computeNewScore(articleStreamMessage);
        ApArticle apArticle = this.getById(articleStreamMessage.getArticleId());
        ApArticle cache = new ApArticle();
        cache.setId(apArticle.getId());
        cache.setTitle(apArticle.getTitle());
        cache.setStaticUrl(apArticle.getStaticUrl());
        cache.setImages(apArticle.getImages());
        cache.setLayout(apArticle.getLayout());
        cache.setAuthorId(apArticle.getAuthorId());
        cache.setAuthorName(apArticle.getAuthorName());
        cache.setChannelId(apArticle.getChannelId());

//        1、更新Redis中这个文章的分值
//        是否能从Redis中获取这个文章
        Double score = redisTemplate.boundZSetOps("hot_article_first_page_" + apArticle.getChannelId()).score(JSON.toJSONString(cache));


        if (score == null) {  //Redis中没有这个数据
            redisTemplate.boundZSetOps("hot_article_first_page_" + apArticle.getChannelId()).add(JSON.toJSONString(cache), newScore);
            redisTemplate.boundZSetOps("hot_article_first_page_0").add(JSON.toJSONString(cache), newScore);
        } else {
            redisTemplate.boundZSetOps("hot_article_first_page_" + apArticle.getChannelId()).add(JSON.toJSONString(cache), newScore + score);
            redisTemplate.boundZSetOps("hot_article_first_page_0").add(JSON.toJSONString(cache), newScore + score);
        }

//         2、更新这个文章在mysql中的各个数量
//        update ap_article set likes=? ,collection=?,comment=?,views=? where id=??
        LambdaUpdateWrapper<ApArticle> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.setSql("likes=likes+" + articleStreamMessage.getLike());
        updateWrapper.setSql("collection=collection+" + articleStreamMessage.getCollect());
        updateWrapper.setSql("comment=comment+" + articleStreamMessage.getComment());
        updateWrapper.setSql("views=views+" + articleStreamMessage.getView());
        updateWrapper.eq(ApArticle::getId, articleStreamMessage.getArticleId());
        this.update(updateWrapper);
    }


    private Long computeNewScore(ArticleStreamMessage articleStreamMessage) {
        Long newScore = 0L;
        newScore += articleStreamMessage.getView();
        newScore += articleStreamMessage.getLike() * 3;
        newScore += articleStreamMessage.getComment() * 5;
        newScore += articleStreamMessage.getCollect() * 8;
        return newScore * 3;

    }


}
